package com.xx.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.github.yulichang.toolkit.JoinWrappers;
import com.xx.common.constant.Constants;
import com.xx.linkage.chain.DisposeChain;
import com.xx.service.*;
import com.xx.utils.giu.BmapPoint;
import com.xx.utils.giu.GraphUtils;
import com.xx.utils.redis.RedisUtil;
import com.xx.web.domain.dto.EnvDto;
import com.xx.web.domain.dto.WarningInfo;
import com.xx.web.domain.entity.*;
import com.xx.web.mapper.NavigationEnvMapper;
import com.xx.web.proto.ShipOuterClass;
import com.xx.web.service.IEarlyWarningRuleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.*;

/**
 * @author Xander
 * @version 1.0
 * @ClassName: SystemInitServiceImpl
 * @Description:
 * @Date: 2023/5/24 16:37
 * @since JDK 1.8
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class RuleCalcServiceImpl implements RuleCalcService {

    private final IWhiteListService whiteListService;

    private final IBlackListService blackListService;

    private final NavigationEnvMapper envMapper;

    private final RedisUtil redisUtil;

    private final IShipRorService shipRorService;

    private final IEarlyWarningRuleService warningRuleService;

    private final IDisposeRuleService disposeRuleService;

    private final DisposeChain disposeChain;

    public static List<String> envAreas = Arrays.asList(Constants.ONE_WARNING, Constants.TWO_WARNING,
            Constants.THREE_WARNING,
            Constants.MONITORING,
            Constants.SHIPPING_LANE,
            Constants.HARBOR,
            Constants.ANCHORAGE_GROUND);

    /**
     * 初始化配置
     */
    @PostConstruct
    public void init() {
        clearCache();
        initEfConfig();
    }

    public void initEfConfig() {
        //Initialize the black and white lists
        initBWList();
        //Initialize the warning area, waterway, monitoring area, etc
        initEnvArea();
        //Initialize alert rules and dispose of rules
        initRules();

    }

    public void clearCache() {
        //clear the black and white lists
        clearBWList();
        //clear the warning area, waterway, monitoring area, etc
        clearEnvArea();
        //clear alert rules and dispose of rules
        clearRules();
    }

    private void clearRules() {
        redisUtil.del(Constants.WARNING_RULES);
        redisUtil.del(Constants.DISPOSE_RULE);
    }

    private void clearEnvArea() {
        envAreas.forEach(v ->
                redisUtil.del(getEnvKey(v))
        );
    }

    /**
     * 清理黑白名单缓存
     */
    private void clearBWList() {
        redisUtil.del(Constants.WHITE_LIST);
        redisUtil.del(Constants.BLACK_LIST);
    }

    /**
     * 初始化预警规则和处置规则
     */
    private void initRules() {
        //预警规则
        warningRuleService.warningRules();
        //处置规则
        disposeRuleService.disposeRules();

    }

    /**
     * initialize the black and white lists
     */
    private void initBWList() {
        LocalDateTime now = LocalDateTime.now();
        //白名单列表
        whiteListService.whiteList(now);
        //黑名单列表
        blackList(now);

    }

    public void blackList(LocalDateTime now) {
        List<BlackList> blackLists = blackListService.list(Wrappers.<BlackList>lambdaQuery()
                .ge(BlackList::getEndTime, now));
        blackLists.forEach(b ->
                redisUtil.hmSet(Constants.BLACK_LIST, b.getMmsi(), b)
        );
    }




    /**
     * Initialize Navigable Navigable environment
     */
    private void initEnvArea() {
        //预警区
        envAreas.forEach(this::insertEnv2Redis);
    }

    public void insertEnv2Redis(String envType) {
        List<NavigationEnv> navigationEnvs = envMapper.selectJoinList(NavigationEnv.class,
                JoinWrappers.lambda(NavigationEnv.class)
                        .select(NavigationEnv::getId, NavigationEnv::getName, NavigationEnv::getEnvType,
                                NavigationEnv::getEnvTypeId,
                                NavigationEnv::getRegions, NavigationEnv::getGroup, NavigationEnv::getPlace)
                        .leftJoin(NavigationEnvType.class, NavigationEnvType::getId,
                                NavigationEnv::getEnvTypeId)
                        .eq(NavigationEnvType::getIsFence, true)
                        .eq(NavigationEnvType::getEnvKey, envType));

        Map<String, List<EnvDto>> map = new HashMap<>();
        navigationEnvs.forEach(nav -> {
            EnvDto envDto = new EnvDto();
            BeanUtils.copyProperties(nav, envDto);
            List<EnvDto> envDtos = map.get(nav.getGroup());
            if (envDtos == null) {
                envDtos = new ArrayList<>();
                envDtos.add(envDto);
                map.put(nav.getGroup(), envDtos);
            } else {
                envDtos.add(envDto);
            }
        });
        map.forEach((k, v) ->
                redisUtil.hmSet(getEnvKey(envType), k, v)
        );
    }

    /**
     * 获取通航环境redis key
     *
     * @param key
     * @return
     */
    public String getEnvKey(String key) {
        return getEnvKey(key, null);
    }

    /**
     * get navigation environment redis key
     *
     * @param key   redis key
     * @param group 通航环境组号
     * @return
     */
    public String getEnvKey(String key, String group) {
        if (StringUtils.isBlank(group)) {
            return Constants.ENV_AREA + key;
        }
        return Constants.ENV_AREA + key + ":" + group;
    }


    /**
     * 计算规则
     *
     * @param ship
     */
    @Override
    public void calcRule(ShipOuterClass.Ship ship) {
        //log.info("开始计算规则~~ {}", ship.getId());
        List<String> warningAreas = Arrays.asList(Constants.ONE_WARNING, Constants.TWO_WARNING,
                Constants.THREE_WARNING,
                Constants.MONITORING);
/*        for (String warningArea : warningAreas) {
            calcWarning(warningArea, ship);
        }*/
        //
/*        if (redisUtil.hashExists(Constants.BLACK_LIST, Integer.toString(ship.getMmsi()))) {

        }*/
    }

    @Override
    public void calcWarning(ShipOuterClass.Ship ship) {

        //判断黑名单 如果在黑名单 直接报警
        //judgeBlackList(ship);

        //判断之前是否触发过了预警
        WarningInfo warningInfo = redisUtil.get(Constants.WARNING_LIST + ship.getId());
        if (warningInfo != null) { //触发过预警
            //判断是几级预警
            //log.info("触发过预警开始计算11 {}", ship.getId());
            switch (warningInfo.getEnvType()) {
     /*           case Constants.ONE_WARNING: {
                    //一级预警处理
                    //如果是一级预警是否需要再次报警:
                    break;
                }*/
                case Constants.TWO_WARNING: {
                    //二级预警处理
                    //上次是二级预警:判断这次是几级,如果是一级马上报警 记录 联动
                    judgeWarning(Constants.ONE_WARNING, warningInfo, ship);
                    break;
                }
                case Constants.THREE_WARNING: {
                    //三级预警处理:判断这次是几级,如果是一,二级马上报警 记录 联动
                    if (!judgeWarning(Constants.ONE_WARNING, warningInfo, ship)) {
                        judgeWarning(Constants.TWO_WARNING, warningInfo, ship);
                    }
                    break;
                }
                default:
                    break;
            }
            //TODO 判断同组预警区
        } else {
            //没有触发过预警 从三级开始计算
            //log.info("没有触发过预警开始计算~~ {}", ship.getId());
            if (!judgeWarning(Constants.ONE_WARNING, null, ship)) {
                if (!judgeWarning(Constants.TWO_WARNING, null, ship)) {
                    judgeWarning(Constants.THREE_WARNING, null, ship);
                }
            }
            //偏离航道计算
            judgeWarning(Constants.SHIPPING_LANE, null, ship);
        }
        //isInWaringArea(warningArea, null, ship);
    }

    /**
     * @Description: 判断预警区域
     * @author: xx
     * @Date: 2023/5/30
     * @param0: envType
     * @param1: warningInfo
     * @param2: ship
     * @paramTypes: [java.lang.String, com.xx.web.domain.dto.WarningInfo, com.xx.proto.ShipOuterClass.Ship]
     * @return: void
     */
    private boolean judgeWarning(String envType, WarningInfo warningInfo, ShipOuterClass.Ship ship) {
        EnvDto envDto;
        if (warningInfo == null) {
            envDto = isInWaringArea(envType, null, ship);
        } else {
            envDto = isInWaringArea(envType, warningInfo.getGroup(), ship);
        }

        if (Objects.nonNull(envDto)) {
            //log.info("在预警区内: {} {}" ,warningInfo,ship.getId());
            boolean b = judgeWhiteList(envType, ship);
            if (!b) {
                //不在白名单内
                //预警
                //log.info("不在白名单:{}", ship.getId());
                triggerEarlyWarning(envDto, ship);
                //TODO 联动
                return Boolean.TRUE;
            }
            //TODO 在白名单内
            log.info("---白名单内: {}", ship.getMmsi());
            return Boolean.TRUE;
        }
        return Boolean.FALSE;
    }

    /**
     * 判断是否在黑名单
     */
    private void judgeBlackList(ShipOuterClass.Ship ship) {
        if (redisUtil.hashExists(Constants.BLACK_LIST, ship.getMmsi() + "")) {
            BlackList blackList = redisUtil.hmGet(Constants.BLACK_LIST, ship.getMmsi());
            if (blackList != null) {
                LocalDateTime now = LocalDateTime.now();
                if (now.isBefore(blackList.getEndTime()) && now.isAfter(blackList.getStartTime())) {
                    //在黑名单有效期内
                    //触发预警 执行规则
                    //executeDisposeRule(warningArea, ship);
                }
            }
        }
    }

    /**
     * 执行规则
     *
     * @param warningArea
     * @param ship
     */
    private void executeDisposeRule(String warningArea, ShipOuterClass.Ship ship) {
        //根据预警区获取处置规则

    }


    /**
     * 判断是否在白名单
     *
     * @param warningLevel
     * @param ship
     */
    private boolean judgeWhiteList(String warningLevel, ShipOuterClass.Ship ship) {
        //判断白名单 在白名单就不需要设备联动, 记录留档即可
        WhiteList whiteList = redisUtil.hmGet(Constants.WHITE_LIST, ship.getMmsi() + "");
        if (whiteList != null) {
            LocalDateTime now = LocalDateTime.now();
            if (whiteList.getEndTime().isAfter(now) && whiteList.getStartTime().isBefore(now)) {
                //在白名单内
                //留档 retention of records
                //ShipRor shipRor = ShipConverter.INSTANCE.source2Target(ship);
                //shipRor.setName(StringUtils.isEmpty(ship.getChineseName()) ? ship.getName() : ship.getChineseName());
                //// TODO 触发预警时的航迹 shipRor.setTrack();
                //// shipRor.setTrack(...);
                //shipRor.setEnvType(warningLevel);
                //shipRorService.save(shipRor);
                return true;
            }

        }
        return false;

    }

    /**
     * 触发预警
     *
     * @param envDto
     * @param ship
     */
    private void triggerEarlyWarning(EnvDto envDto, ShipOuterClass.Ship ship) {
/*        switch (envDto.getEnvKey()) {
            case Constants.ONE_WARNING: {
                //获取一级预警的处置规则
                calcDisposeRule(envDto, ship);

            }
            case Constants.TWO_WARNING: {

            }
            case Constants.THREE_WARNING: {

            }
        }*/
        calcDisposeRule(envDto, ship);

    }

    private void calcDisposeRule(EnvDto envDto, ShipOuterClass.Ship ship) {
        DisposeRule disposeRule = redisUtil.hmGet(Constants.DISPOSE_RULE, envDto.getEnvType());
        if (disposeRule == null) {
            log.error("处置规则没有配置{}",envDto.getEnvType());
        }else {
            //有效时间判断
            LocalDateTime now = LocalDateTime.now();
            LocalTime nowTime = now.toLocalTime();
            if (disposeRule.getStatus()) {
                if(now.isAfter(disposeRule.getStartDate()) && now.isBefore(disposeRule.getEndDate())
                        && !nowTime.isBefore(disposeRule.getStartTimeDay()) && !nowTime.isAfter(disposeRule.getEndTimeDay())){
                    disposeChain.handle(disposeRule, ship, envDto);
                }
            }
        }
    }

    /**
     * @Description: 判断是否在预警区域
     * @author: Xander
     * @Date: 2023/5/30
     * @param0: warningArea
     * @param1: group    查询所有组就传null
     * @param2: ship
     * @return: com.xx.web.domain.dto.EnvDto
     */
    private EnvDto isInWaringArea(String warningArea, String group, ShipOuterClass.Ship ship) {
        BmapPoint bmapPoint = new BmapPoint(ship.getLon(), ship.getLat());
        //log.info("判断是否在预警区域");
        //TODO
        if (group == null) {
            Map<String, List<EnvDto>> envListMap = redisUtil.hGetAll(getEnvKey(warningArea),
                    (Class<List<EnvDto>>) (Class<?>) List.class);
            if (!CollectionUtils.isEmpty(envListMap)) {
                for (Map.Entry<String, List<EnvDto>> entry : envListMap.entrySet()) {
                    for (EnvDto envDto : entry.getValue()) {
                        if (GraphUtils.isPointInPolygons(bmapPoint, envDto.getRegions())) {//进入了预警区
                            //TODO 规则判断
                            log.info("在预警区域1 {} {}", ship.getId(), warningArea);
                            return envDto;
                        }
                    }
                }
            }
            //没有进入预警区
            return null;
        } else {
            List<EnvDto> list = redisUtil.hmGet(getEnvKey(warningArea), group);
            if (CollectionUtils.isEmpty(list)) {
                return null;
            }
            for (EnvDto envDto : list) {
                if (GraphUtils.isPointInPolygons(bmapPoint, envDto.getRegions())) {//进入了预警区
                    log.info("在预警区域2 {} {}", ship.getId(), warningArea);
                    return envDto;
                }
            }
            return null;
        }
    }
}
